package cn.yrec.rain.analysis.service.impl;

import cn.yrec.rain.analysis.exception.ErrorResult;
import cn.yrec.rain.analysis.exception.ModelDataException;
import cn.yrec.rain.analysis.exception.RequestParamException;
import cn.yrec.rain.analysis.model.RaPDayRt;
import cn.yrec.rain.analysis.program.CalculateCore;
import cn.yrec.rain.analysis.service.AnalysisService;
import cn.yrec.rain.analysis.service.DataProcessService;
import cn.yrec.rain.analysis.service.RemoteApiService;
import cn.yrec.rain.analysis.utils.DateUtils;
import cn.yrec.rain.analysis.vo.FindRainRequestParam;
import cn.yrec.rain.analysis.vo.RaPDayRtVo;
import cn.yrec.rain.analysis.vo.RainModel;
import cn.yrec.rain.analysis.vo.RainfallResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * 描述:
 *  相似性分析业务层接口实现类
 * @createDate: 2023/03/30 22:42
 * @author: yanglichen
 */
@Service
public class AnalysisServiceImpl implements AnalysisService {

    @Autowired
    private DataProcessService dataProcessService;
    @Autowired
    private RemoteApiService remoteApiService;

    /**
     * 方法描述: 根据请求参数找到场次雨
     *
     * @param requestParam 请求参数
     * @return 场次雨列表
     * @author yanglichen
     * @date 2023-03-30 22:44
     **/
    @Override
    public List<RainfallResult> findRain(FindRainRequestParam requestParam) {
        // 数据有效性验证
        requestParamCannotBeNull(requestParam);
        // 初始化模型对象
        RainModel rainModel = new RainModel();
        // 组装请求模型参数
        setRequestParamIntoRainModel(rainModel, requestParam);
        // 组装雨量站天数统计对象
        setRainfallCountMap(rainModel);
        // 降雨数据大集合数据
        setAllInformationMap(rainModel);
        // 数据完整性检查
        rainDataCannotBeBull(rainModel);
        // 模型计算
        CalculateCore instance = CalculateCore.getInstance();
        instance.findRain(rainModel);
        return rainModel.getPrototypeRainfallList();
    }

    private void rainDataCannotBeBull(RainModel rainModel) {
        Map<Date, Integer> rainfallCountMap = rainModel.getRainfallCountMap();
        if (rainfallCountMap == null || rainfallCountMap.keySet().size()==0) {
            throw new ModelDataException(ErrorResult.modelDataIsNull());
        }

        Map<String, Map<Date, RaPDayRtVo>> allInformationMap = rainModel.getAllInformationMap();
        if (allInformationMap == null ) {
            throw new ModelDataException(ErrorResult.modelDataIsNull());
        }
        Collection<Map<Date, RaPDayRtVo>> allMapValues = allInformationMap.values();
        boolean f = true;
        for (Map<Date, RaPDayRtVo> itemValues : allMapValues) {
            if (itemValues != null) {
                f = false;
                break;
            }
        }
        if (f) {
            throw new ModelDataException(ErrorResult.modelDataIsNull());
        }
    }

    /**
     * 方法描述: 根据原形雨进行降雨相似性分析
     *
     * @param requestParam 请求参数
     * @author yanglichen
     * @date 2023-04-03 11:12
     **/
    @Override
    public List<RainfallResult> conformAnalysis(RainfallResult requestParam) {
        // 构造模型参数
        RainModel rainModel = new RainModel();
        // 组装请求模型参数
        setRequestParamIntoRainModelForConform(rainModel, requestParam);
        // 组装雨量站天数统计对象
        setRainfallCountMap(rainModel);
        // 降雨数据大集合数据
        setAllInformationMap(rainModel);
        // 组装降雨天数和开始日期的数据
        setRainfallTakeTimeAndDate(rainModel);
        // 调用模型
        CalculateCore instance = CalculateCore.getInstance();
        instance.findByRainfall(rainModel);
        // 得到场次雨列表
        return rainModel.getResembleRainfallList();
    }

    /**
     * 方法描述: 根据流域名称寻找原形雨
     *
     * @param requestParam 请求参数
     * @return 降雨结果
     * @author yanglichen
     * @date 2023-05-24 17:37
     **/
    @Override
    public List<RainfallResult> findRainByBasinName(FindRainRequestParam requestParam) {
        List<String> basinNameList = requestParam.getBasinNameList();
        // 得到流域列表
        List<String> stcdList = remoteApiService.getStcdListByBasinNameList(basinNameList);
        for (String item : stcdList) {
            System.out.println("'"+item+"',");
        }
        // 赋值站码列表
        requestParam.setStcdList(stcdList);
        return findRain(requestParam);
    }

    /**
     * 方法描述: 通过开始时间和结束时间，站码列表，降雨阈值，判断这些站码列表中，那些是有数据的数据
     *
     * @param param 请求参数
     * @return 有降雨的雨量站的数据
     * @author yanglichen
     * @date 2023-08-21 10:25
     **/
    @Override
    public List<RaPDayRt> judgeRain(FindRainRequestParam param) {
        List<RaPDayRt> result = new ArrayList<>();
        // 开始时间
        Date startDate = param.getStartDate();
        // 结束时间
        Date endDate = param.getEndDate();
        // 站码列表
        List<String> stcdList = param.getStcdList();
        // 降雨阈值
        Double rainfallThreshold = param.getRainfallParameters().getRainfallThreshold();

        // 得到区间时段内的累计雨量数据
        List<RaPDayRt> sumRainData =  dataProcessService.getSumRainByStartDateEndDateStcdListAndRainThreshold(
                startDate, endDate, stcdList, rainfallThreshold);
        // 过滤降雨为0的数据
        for (RaPDayRt item : sumRainData) {
            if (item.getRf()>=0) {
                result.add(item);
            }
        }
        return result;
    }

    /**
     * 方法描述:  封装根据原型雨在数据库中寻找所有的备选雨
     * @author yanglichen
     * @date 2023-04-03 15:26
     * @param rainModel 降雨参数
     **/
    private void setRainfallTakeTimeAndDate(RainModel rainModel) {
        //构造结果
        Map<Integer, List<Date>> map = recursiveGetRainfallTakeTime(rainModel);
        rainModel.setRainTakeDaysAndDateListMap(map);
    }

    /**
     * 方法描述: 根据原型雨在数据库中寻找所有的备选雨
     * 方法(伪递归: 根据一场雨的开始时间,寻找上一场雨的结束时间,在根据结束时间,找到开始时间,
     * 再根据这次寻找到的上一次雨的开始时间,去继续向上寻找)
     *
     * @param rainModel   降雨参数
     * @return 历史中不同历时对应的不同日期
     * 例:{5天:[2020/5/1, 2020/5/20], 6天:[2019/4/11, 2019/6/22].....}
     * @author yanglichen
     * @date 2020-07-17 17:02
     **/
    private Map<Integer, List<Date>> recursiveGetRainfallTakeTime(RainModel rainModel){
        // 构造结果
        Map<Integer, List<Date>> result = new HashMap<>();
        // 得到原形雨降雨的结束时间
        Date endRainfallDate = new Date(rainModel.getPrototypeRainfall().getEndRainfallDate().getTime());
        // 得到降雨的历时的控制参数
        Integer timeInterval = rainModel.getRainfallParameters().getTimeInterval();
        // 得到降雨日期对应的计数项
        Map<Date, Integer> dateCountMap = dataProcessService.getRainfallCountMap(rainModel);
        // 得到最早的日期
        Date pioneerDate = getEndRainfallDate(dateCountMap);
        rainModel.setPioneerDate(pioneerDate);
        while (true) {
            // 得到降雨开始的日期
            Date rainfallStartDate = findRainfallStartDate(dateCountMap, endRainfallDate, timeInterval, pioneerDate);
            // 得到降雨结束的日期
            Date rainfallEndDate = findRainfallEndDate(dateCountMap, rainfallStartDate, timeInterval);
            //得到降雨历时
            Integer rainfallTakeTimes = DateUtils.getTimesByStartDateAndEndDate(rainfallEndDate, rainfallStartDate);
            //存放一轮数据
            if (result.get(rainfallTakeTimes) != null) {
                result.get(rainfallTakeTimes).add(rainfallStartDate);
            } else {
                List<Date> dateList = new ArrayList<>();
                dateList.add(rainfallStartDate);
                result.put(rainfallTakeTimes, dateList);
            }
            //替换时间,得到下一次降雨的结束日期
            Date nextRainfallEndDate = getNextRainfallEndDate(dateCountMap, rainfallStartDate);
            if (nextRainfallEndDate == null) {
                return result;
            }
            //跳出条件:到头了
            if (nextRainfallEndDate.getTime() - pioneerDate.getTime()<=DateUtils.ONE_DAY_MILLI_SECOND*3) {
                return result;
            }
            endRainfallDate.setTime(nextRainfallEndDate.getTime());
        }
    }


    private Date getNextRainfallEndDate(Map<Date, Integer> rainfallDateCount, Date rainfallStartDate) {
        Date tempDate = new Date(rainfallStartDate.getTime());
        while (true) {
            tempDate.setTime(tempDate.getTime()-DateUtils.ONE_DAY_MILLI_SECOND);
            if (rainfallDateCount.get(tempDate)!=null) {
                return new Date(tempDate.getTime());
            }
            if (tempDate.getTime()<0) {
                return null;
            }
        }
    }

    /**
     * 方法描述: 得到降雨的结束时间
     * @author yanglichen
     * @date 2023-04-03 15:39
     * @param rainfallDateCount 日期和降雨天数的Map
     * @param rainfallStartDate 降雨开始时间
     * @param timeInterval 降雨间隔
     * @return 降雨的结束时间
     **/
    private Date findRainfallEndDate(Map<Date, Integer> rainfallDateCount, Date rainfallStartDate, Integer timeInterval) {
        Date tempDate = new Date(rainfallStartDate.getTime());
        int timesCount = 0;
        while(true){
            if(rainfallDateCount.get(tempDate)==null){
                timesCount++;
            }else{
                timesCount = 0;
            }
            //日期增加一天
            if(timesCount >= timeInterval){
                break;
            }
            tempDate.setTime(tempDate.getTime() + DateUtils.ONE_DAY_MILLI_SECOND);
            // 跳出条件
            if (tempDate.getTime() >= new Date().getTime()) {
                break;
            }
        }
        return new Date(tempDate.getTime() - timeInterval*DateUtils.ONE_DAY_MILLI_SECOND);
    }

    /**
     * 方法描述: 得到通过降雨历时和降雨的结束时间找到降雨的开始时间
     * @author yanglichen
     * @date 2023-04-03 15:34
     * @param rainfallDateCount 日期和对应天数的计数项
     * @param rainfallEndDate 降雨结束时间
     * @param timeInterval 时间间隔
     **/
    private Date findRainfallStartDate(
            Map<Date, Integer> rainfallDateCount, Date rainfallEndDate,
            Integer timeInterval, Date pioneerDate ) {
        /*
        新的计算方法
        倒推数据
        知道直到找到有降雨的那一天
        这一天作为降雨截止日
        */
        while (rainfallDateCount.get(rainfallEndDate) == null) {
            rainfallEndDate.setTime(rainfallEndDate.getTime() - DateUtils.ONE_DAY_MILLI_SECOND);
        }
        //将上一场雨/计算的时间 赋值给一个用于内部处理的临时时间对象
        Date tempDate = new Date(rainfallEndDate.getTime());
        //初始化时间历时,用于判断场次雨的间隔
        int timesCount = 0;
        while(true){
            //如果找不到这一天的数据
            if(rainfallDateCount.get(tempDate)==null){
                //时间历时+1
                timesCount++;
            }else{
                //找到了重新设置为0
                timesCount = 0;
            }
            //如果时间历时 >= 跳出的历时范围
            if (timesCount >= timeInterval) {
                break;
            }
            //时间减一天
            tempDate.setTime(tempDate.getTime() - DateUtils.ONE_DAY_MILLI_SECOND);
            // 到头了就跳出
            if (tempDate.getTime() <= pioneerDate.getTime()) {
                break;
            }
        }
        return new Date(tempDate.getTime() + timeInterval*DateUtils.ONE_DAY_MILLI_SECOND);
    }

    /**
     * 方法描述: 得到降雨最早的开始时间
     * @author yanglichen
     * @date 2023-04-03 15:28
     * @param rainfallDateCount 降雨天数的Map
     * @return 最早的日期
     **/
    private Date getEndRainfallDate(Map<Date, Integer> rainfallDateCount) {
        Set<Date> dateSet = rainfallDateCount.keySet();
        List<Date> dateList = new ArrayList<>(dateSet);
        long minDateTime = Long.MAX_VALUE;
        for (Date date : dateList) {
            if(date!=null){
                if (date.getTime() < minDateTime) {
                    minDateTime = date.getTime();
                }
            }
        }
        return new Date(minDateTime);
    }

    private void setRequestParamIntoRainModelForConform(RainModel rainModel, RainfallResult requestParam) {
        rainModel.setPrototypeRainfall(requestParam);
        rainModel.setStartDate(requestParam.getRainfallDate());
        rainModel.setEndDate(requestParam.getEndRainfallDate());
        rainModel.setStcdList(requestParam.getRainfallParameters().getStcdList());
        rainModel.setRainfallParameters(requestParam.getRainfallParameters());
    }

    /**
     * 方法描述: 封装所有的降雨数据的对象
     * @author yanglichen
     * @date 2023-04-02 16:21
     * @param rainModel 模型对象类
     **/
    private void setAllInformationMap(RainModel rainModel) {
        Map<String, Map<Date, RaPDayRtVo>> allInformationMap = dataProcessService.getAllInformationMap(rainModel);
        rainModel.setAllInformationMap(allInformationMap);
    }

    /**
     * 方法描述: 封装雨量站对应天数的计数项数据
     * @author yanglichen
     * @date 2023-04-02 16:21
     * @param rainModel 模型对象类
     **/
    private void setRainfallCountMap(RainModel rainModel) {
        // 得到雨量站对应降雨天数的计数项的数据
        Map<Date, Integer> rainfallCountMap = dataProcessService.getRainfallCountMap(rainModel);
        // 得到最早的日期
        Date pioneerDate = getEndRainfallDate(rainfallCountMap);
        // 数据处理
        rainModel.setPioneerDate(pioneerDate);
        rainModel.setRainfallCountMap(rainfallCountMap);
    }

    /**
     * 方法描述: 将请求的模型参数封装至降雨模型参数中
     * @author yanglichen
     * @date 2023-03-31 00:06
     * @param rainModel 降雨模型对象
     * @param requestParam 请求参数
     **/
    private void setRequestParamIntoRainModel(RainModel rainModel, FindRainRequestParam requestParam) {
        requestParam.getRainfallParameters().setStcdList(requestParam.getStcdList());
        requestParam.getRainfallParameters().setBasinNameList(requestParam.getBasinNameList());
        rainModel.setRainfallParameters(requestParam.getRainfallParameters());
        rainModel.setStcdList(requestParam.getStcdList());
        rainModel.setStartDate(requestParam.getStartDate());
        rainModel.setEndDate(requestParam.getEndDate());
        rainModel.setBasinNameList(requestParam.getBasinNameList());
    }

    /**
     * 方法描述: 请求参数不可为空的判定
     * @author yanglichen
     * @date 2023-03-30 23:42
     * @param requestParam 请求参数
     **/
    private void requestParamCannotBeNull(FindRainRequestParam requestParam) {
        // 请求参数为空
        if (requestParam.getRainfallParameters() == null) {
            throw new RequestParamException(ErrorResult.requestParamCannotBeNull());
        }
        // 开始时间为空
        if (requestParam.getStartDate() == null) {
            throw new RequestParamException(ErrorResult.startDateCannotBeNull());
        }
        // 结束时间为空
        if (requestParam.getEndDate() == null) {
            throw new RequestParamException(ErrorResult.endDateCannotBeNull());
        }
    }
}
